﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Reflection;
using Ue.Core.FileSystems;
using Ue.Infrastructure.Caching;
using Ue.Infrastructure.Caching.VolatileProvider;
using Ue.Eagle.Runtime.FileSystems;
using Ue.Eagle.Runtime.FileSystems.Dependencies;
using Ue.Eagle.Runtime.Models;

namespace Ue.Eagle.Runtime.Loaders
{
    /// <summary>
    /// 已经编译过，并且dll文件存放于插件目录下的bin目录中的插件。
    /// </summary>
    public class PrecompiledPluginLoader : PluginLoaderBase
    {
        private readonly IHostEnvironment hostEnvironment;
        private readonly IAssemblyProbingFolder assemblyProbingFolder;
        private readonly IRootFolder rootFolder;
        //private readonly IFileMonitor virtualPathMonitor;

        public PrecompiledPluginLoader(
            IHostEnvironment hostEnvironment,
            IDependenciesFolder dependenciesFolder,
            IAssemblyProbingFolder assemblyProbingFolder,
            IRootFolder rootFolder)
            : base(dependenciesFolder)
        {
            this.hostEnvironment = hostEnvironment;
            this.assemblyProbingFolder = assemblyProbingFolder;
            this.rootFolder = rootFolder;
        }

        public bool Disabled { get; set; }

        public override int Priority { get { return 30; } }

        public override IEnumerable<PluginCompilationReference> GetCompilationReferences(DependencyDescriptor dependency)
        {
            yield return new PluginCompilationReference { AssemblyName = dependency.Name };
        }

        public override IEnumerable<string> GetVirtualPathDependencies(DependencyDescriptor dependency)
        {
            yield return this.assemblyProbingFolder.GetAssemblyVirtualPath(dependency.Name);
        }

        public override void PluginRemoved(PluginLoadingContext ctx, DependencyDescriptor dependency)
        {
            if (this.assemblyProbingFolder.AssemblyExists(dependency.Name))
            {
                ctx.DeleteActions.Add(() => this.assemblyProbingFolder.DeleteAssembly(dependency.Name));

                // 如何插件Assemble是已经已经加载到系统中，那么则需要重新启动系统
                if (this.hostEnvironment.IsAssemblyLoaded(dependency.Name))
                {
                    ctx.RestartAppDomain = true;
                }
            }
        }

        public override void PluginActivated(PluginLoadingContext ctx, PluginDescriptor pluginDescriptor)
        {
            string sourceFileName = this.rootFolder.ToPhysicalPath(GetAssemblyPath(pluginDescriptor));

            // 当dll文件不存在，或比当前dll文件旧时，复制dll文件
            bool copyAssembly =!this.assemblyProbingFolder.AssemblyExists(pluginDescriptor.Id) 
                || File.GetLastWriteTimeUtc(sourceFileName) > this.assemblyProbingFolder.GetAssemblyDateTimeUtc(pluginDescriptor.Id);

            if (copyAssembly)
            {
                ctx.CopyActions.Add(() => this.assemblyProbingFolder.StoreAssembly(pluginDescriptor.Id, sourceFileName));

                //如何插件Assemble没有加载到系统中，那么则需要重新启动系统
                if (this.hostEnvironment.IsAssemblyLoaded(pluginDescriptor.Id))
                {
                    ctx.RestartAppDomain = true;
                }
            }
        }

        public override void PluginDeactivated(PluginLoadingContext ctx, PluginDescriptor pluginDescriptor)
        {
            if (this.assemblyProbingFolder.AssemblyExists(pluginDescriptor.Id))
            {
                ctx.DeleteActions.Add(() =>this.assemblyProbingFolder.DeleteAssembly(pluginDescriptor.Id));

                // We need to restart the appDomain if the assembly is loaded
                if (this.hostEnvironment.IsAssemblyLoaded(pluginDescriptor.Id))
                {
                    ctx.RestartAppDomain = true;
                }
            }
        }

        public override void ReferenceActivated(PluginLoadingContext context, PluginReferenceProbeEntry referenceEntry)
        {
            if (string.IsNullOrEmpty(referenceEntry.VirtualPath))
                return;

            string sourceFileName = this.rootFolder.ToPhysicalPath(referenceEntry.VirtualPath);

            // 当dll文件不存在，或比当前dll文件旧时，复制dll文件
            bool copyAssembly =!this.assemblyProbingFolder.AssemblyExists(referenceEntry.Name) 
                || File.GetLastWriteTimeUtc(sourceFileName) > this.assemblyProbingFolder.GetAssemblyDateTimeUtc(referenceEntry.Name);

            if (copyAssembly)
            {
                context.CopyActions.Add(() => this.assemblyProbingFolder.StoreAssembly(referenceEntry.Name, sourceFileName));

                // We need to restart the appDomain if the assembly is loaded
                if (this.hostEnvironment.IsAssemblyLoaded(referenceEntry.Name))
                {
                    context.RestartAppDomain = true;
                }
            }
        }

        //public override void Monitor(PluginDescriptor descriptor, Action<IVolatileToken> monitor)
        //{
        //    if (Disabled)
        //        return;

        //    // If the assembly exists, monitor it
        //    string assemblyPath = GetAssemblyPath(descriptor);
        //    if (assemblyPath != null)
        //    {
        //        monitor(this.virtualPathMonitor.WhenPathChanges(assemblyPath));
        //        return;
        //    }

        //    // If the assembly doesn't exist, we monitor the containing "bin" folder, as the assembly 
        //    // may exist later if it is recompiled in Visual Studio for example, and we need to 
        //    // detect that as a change of configuration.
        //    var assemblyDirectory = this.rootFolder.Combine(descriptor.Location, descriptor.Id, "bin");
        //    if (this.rootFolder.DirectoryExists(assemblyDirectory))
        //    {
        //        monitor(this.virtualPathMonitor.WhenPathChanges(assemblyDirectory));
        //    }
        //}

        public override IEnumerable<PluginReferenceProbeEntry> ProbeReferences(PluginDescriptor descriptor)
        {
            if (Disabled)
                return Enumerable.Empty<PluginReferenceProbeEntry>();

            var assemblyPath = this.GetAssemblyPath(descriptor);
            if (assemblyPath == null)
                return Enumerable.Empty<PluginReferenceProbeEntry>();
            
            var result = this.rootFolder
                .GetFiles(Path.GetDirectoryName(assemblyPath))
                .Where(s => StringComparer.OrdinalIgnoreCase.Equals(Path.GetExtension(s), ".dll"))
                .Where(s => !StringComparer.OrdinalIgnoreCase.Equals(Path.GetFileNameWithoutExtension(s), descriptor.Id))
                .Select(path => new PluginReferenceProbeEntry
                {
                    Descriptor = descriptor,
                    Loader = this,
                    Name = Path.GetFileNameWithoutExtension(path),
                    VirtualPath = path
                })
                .ToList();

            return result;
        }

        public override bool IsCompatibleWithModuleReferences(PluginDescriptor extension, IEnumerable<PluginProbeEntry> references)
        {
            return references.All(r => r.Loader.GetType() != typeof(DynamicPluginLoader));
        }

        public override PluginProbeEntry Probe(PluginDescriptor descriptor)
        {
            if (Disabled)
                return null;

            var assemblyPath = GetAssemblyPath(descriptor);
            if (assemblyPath == null)
                return null;

            var result = new PluginProbeEntry
            {
                Descriptor = descriptor,
                Loader = this,
                VirtualPath = assemblyPath,
                VirtualPathDependencies = new[] { assemblyPath },
            };

            return result;
        }

        public override Assembly LoadReference(DependencyReferenceDescriptor reference)
        {
            if (Disabled)
                return null;

            return this.assemblyProbingFolder.LoadAssembly(reference.Name);
        }

        protected override Plugin LoadWorker(PluginDescriptor descriptor)
        {
            if (Disabled)
                return null;

            var assembly = this.assemblyProbingFolder.LoadAssembly(descriptor.Id);
            if (assembly == null)
                return null;

            return new Plugin
            {
                Descriptor = descriptor,
                Assembly = assembly,
                ExportedTypes = assembly.GetExportedTypes()
            };
        }

        public string GetAssemblyPath(PluginDescriptor descriptor)
        {
            var assemblyPath = this.rootFolder.Combine(descriptor.Location, "bin", descriptor.Id + ".dll");
            if (!this.rootFolder.FileExists(assemblyPath))
                return null;

            return assemblyPath;
        }
    }
}
